<template>
  <div
    class="scroll-container"
    @mouseenter.stop.prevent="state.isContainerHover = true"
    @mouseleave.stop.prevent="state.isContainerHover = false"
  >
    <div class="scrollbar-y">
      <section ref="scrollRef" class="scroll-wrap" @scroll="onMosewheel">
        <div class="content-wrap">
          <slot></slot>
        </div>
      </section>
      <div
        ref="barRef"
        v-show="
          state.heightPre !== 1 &&
          (!containerControl || state.isContainerHover || state.isMove)
        "
        class="scrollbar-track"
      >
        <div
          v-show="show"
          :style="{
            height: `${state.barHeight - 8}px`,
            width: `${thumbWidth}px`,
            '--thumb-color': thumbColor,
            '--thumb-hover-color': thumbHoverColor,
            '--drak-thumb-color': darkThumbColor,
            '--drak-thumb-hover-color': darkThumbHoverColor,
            transform: `translateY(${state.translateY}px)`
          }"
          class="scrollbar-thumb"
          :class="{ hover: state.isHover || state.isMove }"
          @mouseenter.stop.prevent="state.isHover = true"
          @mouseleave.stop.prevent="state.isHover = false"
          @mousedown.stop.prevent="moveStart"
        ></div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup name="ScrollContainer">
  import { debounce } from 'lodash-es'

  const props = defineProps({
    show: {
      type: Boolean,
      default: true
    },
    // 滑块样式
    thumbColor: {
      type: String,
      default: 'rgba(0, 0, 0, 0.15)'
    },
    //滑块宽度
    thumbWidth: {
      type: Number,
      default: 7
    },
    // 是否悬浮于内容区时才显示
    containerControl: {
      type: Boolean,
      default: false
    },
    //滑块悬浮颜色
    thumbHoverColor: {
      type: String,
      default: 'rgba(0, 0, 0, 0.25)'
    },
    darkThumbColor: {
      type: String,
      default: 'rgba(255, 255, 255, 0.2)'
    },
    darkThumbHoverColor: {
      type: String,
      default: 'rgba(255, 255, 255, 0.3) '
    }
  })

  const state = reactive({
    translateY: 0, // 滚动块平移的距离
    heightPre: 0, // 可视高度和内容高度比
    barHeight: 0, // 滑块高度
    isHover: false,
    isMove: false,
    isContainerHover: false,
    clientHeight: 0
  })

  const scrollRef = ref<HTMLElement | null>(null)
  const barRef = ref<HTMLElement | null>(null)

  const unmountResize: Array<() => void> = []

  let moveClientY = 0 // 鼠标点击滑块时，相对滑块的位置
  let trackHeight = 0 // 滚动条轨道高度
  let wrapHeight = 0 // 容器高度（可视高度）
  let wrapContentHeight = 0 // 内容高度（可滚动内容的高度）

  const emits = defineEmits(['top', 'bottom', 'onMoveDown', 'onMoveStart'])

  onMounted(() => {
    unmountResize.push(monitorWindow()) //监听窗口尺寸
    //监听内容元素尺寸
    nextTick(() => {
      initScrollListner()
      unmountResize.push(monitorScrollBar())
    })
  })

  onUnmounted(() => {
    unmountResize.map((item) => item())
  })

  // 监听页面尺寸改变计算滚动条
  const monitorWindow = () => {
    const handleResize = debounce(() => {
      initScrollListner()
    }, 1000)
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }

  //监听内容元素尺寸变化
  const monitorScrollBar = () => {
    const monitorUl = scrollRef.value && scrollRef.value.children[0]
    state.clientHeight = monitorUl?.clientHeight || 0
    let observer: ResizeObserver | null = new ResizeObserver((entries) => {
      entries.forEach((entry) => {
        if (state.clientHeight !== entry.contentRect.height) {
          state.clientHeight = entry.contentRect.height
          initScrollListner()
        }
      })
    })

    monitorUl && observer.observe(monitorUl)

    return () => {
      monitorUl && observer?.unobserve(monitorUl)
      observer = null
    }
  }

  // 计算滚动条高度
  const initScrollListner = () => {
    const scroll = scrollRef.value
    const bar = barRef.value
    if (scroll && bar) {
      wrapContentHeight = scroll.scrollHeight
      wrapHeight = scroll.clientHeight
      trackHeight = bar.clientHeight
      // 容器高度 / 内容高度
      state.heightPre = wrapHeight / wrapContentHeight
      if (
        state.heightPre !== 1 &&
        trackHeight === 0 &&
        !props.containerControl
      ) {
        nextTick(() => {
          initScrollListner()
        })
        return
      }
      // 滑动块的高度 根据 容器和内容的比  乘以 滚动轨道 计算出 滑动块的高度
      state.barHeight = state.heightPre * trackHeight
      state.translateY = scroll.scrollTop * state.heightPre || 0
    }
  }
  // 内容滚动时，计算滑块移动的距离
  const onMosewheel = (e) => {
    // scrollTop页面顶部滚出的高度
    // offsetHeight页面可视区域高度
    // scrollHeight页面正文全文高度
    // state.translateY滚动块平移的距离
    state.translateY = e.target.scrollTop * state.heightPre
    if (state.translateY == 0) {
      // 到达顶部
      emits('top')
    } else if (
      e.target.scrollTop + e.target.offsetHeight ==
      e.target.scrollHeight
    ) {
      // 滚出高度 + 可视区域高度 == 内容高度
      emits('bottom')
    }
  }
  // 鼠标点击滑块时
  const moveStart = (e) => {
    state.isMove = true
    // clientY：当鼠标事件发生时，鼠标相对于浏览器（这里说的是浏览器的有效区域）y轴的位置
    // state.translateY 滚动块平移的距离
    // moveClientY 鼠标点击滑块时，相对滑块的位置
    moveClientY = e.clientY - state.translateY
    document.onmousemove = (e) => moveTo(e)
    document.onmouseup = () => {
      moveEnd()
    }
  }
  // 鼠标移动，改变thumb的位置以及容器scrollTop的位置
  const moveTo = (e) => {
    // 移动时候判断是不是松开，松开就不在执行滑块移动操作
    if (state.isMove) {
      // 移动滑块时，判断时候到达顶部或者底部
      if (e.clientY - moveClientY > trackHeight - state.barHeight) {
        // 滑块到达  底部  就不在改变滑块translateY值
        state.translateY = trackHeight - state.barHeight
      } else if (e.clientY - moveClientY < 0) {
        // 滑块到达  顶部  就不在改变滑块translateY值
        state.translateY = 0
      } else {
        //改变滑块位置
        state.translateY = e.clientY - moveClientY
      }
      // 计算出内容盒子滚出顶部的距离
      scrollRef.value &&
        (scrollRef.value.scrollTop = state.translateY / state.heightPre)
    }
  }
  // 鼠标从滑块松开时，不在监听滑块移动操作
  const moveEnd = () => {
    state.isMove && (state.isMove = false)
  }

  watch(
    () => state.isContainerHover,
    () => {
      nextTick(() => {
        initScrollListner()
      })
    }
  )

  const isScrollMove = ref(false)
  watch(
    () => state.isMove,
    (val) => {
      val ? emits('onMoveStart') : emits('onMoveDown')
      isScrollMove.value = val
    }
  )

  defineExpose({
    isScrollMove
  })
</script>

<style lang="less" scoped>
  [data-theme='dark'] {
    .scrollbar-thumb {
      background-color: var(--drak-thumb-color) !important;
      &.hover {
        background-color: var(--drak-thumb-hover-color) !important;
      }
    }
  }

  .scroll-container {
    height: 100%;

    .content-wrap {
      // height: 100%;
    }
    .scrollbar-y {
      position: relative;
      height: 100%;
      .scroll-wrap {
        height: 100%;
        padding: 15px;
        padding-bottom: 0px;
        overflow-y: scroll;
        scrollbar-width: none;
        -ms-overflow-style: none;
      }
      .scroll-wrap::-webkit-scrollbar {
        display: none;
      }

      .scrollbar-track {
        position: absolute;
        top: 0;
        bottom: 0;
        z-index: 0;

        right: 0;
        padding: 4px 2px;
        .scrollbar-thumb {
          margin: 0 auto;
          border-radius: 6px;
          cursor: pointer;
          transition: height 0.2s;
          background-color: var(--thumb-color);
          min-height: 16px;
          &.hover {
            background-color: var(--thumb-hover-color);
          }
        }
      }
    }
  }
</style>
